package jpsearch;

import java.io.File;
import java.io.StringReader;
import java.io.StringWriter;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.datatype.XMLGregorianCalendar;

import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;

import jpsearchcore.ContentDesciption;
import jpsearchcore.ContentDesciption.CDOrganizationType;
import jpsearchcore.ControlledRatingTermType;
import jpsearchcore.EventType;
import jpsearchcore.ExternalDescription;
import jpsearchcore.ExternalDescriptonType;
import jpsearchcore.ExternalDescriptonType.LiteralValue;
import jpsearchcore.ExternalDescriptonType.ResourceValue;
import jpsearchcore.ExternalDescriptonType.StructuredValue;
import jpsearchcore.ExternalDescriptonType.TagName;
import jpsearchcore.GPSPositioning;
import jpsearchcore.GPSPositioningType;
import jpsearchcore.ImageIdentifierType;
import jpsearchcore.IntegerMatrixType;
import jpsearchcore.JPSearchCore;
import jpsearchcore.JPSearchCoreType;
import jpsearchcore.ObjectFactory;
import jpsearchcore.ObjectType;
import jpsearchcore.OrganizationType;
import jpsearchcore.OriginalImageIdentifier;
import jpsearchcore.Person;
import jpsearchcore.PersonNameType;
import jpsearchcore.PersonType;
import jpsearchcore.Place;
import jpsearchcore.PlaceType;
import jpsearchcore.Publisher;
import jpsearchcore.PublisherType;
import jpsearchcore.Rating;
import jpsearchcore.RegionLocator;
import jpsearchcore.RegionLocatorType;
import jpsearchcore.RegionOfInterest;
import jpsearchcore.RegionOfInterestType;
import jpsearchcore.RightsDescription;
import jpsearchcore.RightsDescriptionType;
import jpsearchcore.Source;
import jpsearchcore.SourceType;
import jpsearchcore.SourceElement;

public class JPSearchMetadataHandler {

	public static JPSearchCore unmarshal(String XMLData) throws Exception{

		try {
			StringReader reader = new StringReader(XMLData);
			JAXBContext jc = JAXBContext.newInstance("jpsearchcore");
			Unmarshaller u = jc.createUnmarshaller();
			JAXBElement element = (JAXBElement)u.unmarshal(reader);

			//JPSearchCoreType			
			JPSearchCoreType core = (JPSearchCoreType)element.getValue();

			return parseJPSearchCoreType(core);

		} catch (JAXBException e) {
			System.out.println("Problem reading XML");
			e.printStackTrace();
			throw new Exception();
		}
	}

	public static JPSearchCore unmarshal(File input){

		try {
			JAXBContext jc = JAXBContext.newInstance("jpsearchcore");
			Unmarshaller u = jc.createUnmarshaller();
			JAXBElement element = (JAXBElement)u.unmarshal(input);

			//JPSearchCoreType			
			JPSearchCoreType core = (JPSearchCoreType)element.getValue();

			return parseJPSearchCoreType(core);

		} catch (JAXBException e) {
			e.printStackTrace();
		}	
		return null;
	}

	static JPSearchCore parseJPSearchCoreType(JPSearchCoreType core){

		JPSearchCore jpsCore = new JPSearchCore();

		try{
			//Identifier
			String id = core.getIdentifier();
			if(id != null && !StringUtils.isBlank(id)){
				jpsCore.setIdentifier(id.trim());
			}			

			//Modifiers [0..inf]
			List<PersonNameType> modifiers = core.getModifiers();
			if(modifiers.size() > 0){
				List<Person> mods = new ArrayList<Person>(); //structure for storing modifiers
				for(PersonNameType p : modifiers){ //iterate through modifiers read
					mods.add(new Person(p));
				}
				jpsCore.setModifiers(mods);
			}

			//Creators [0-inf]
			List<PersonNameType> creators = core.getCreators();
			if(creators.size() > 0){
				List<Person> cs = new ArrayList<Person>(); //structure for storing modifiers
				for(PersonNameType p : creators){ //iterate through modifiers read
					cs.add(new Person(p));
				}
				jpsCore.setCreators(cs);
			}

			//Publishers [0..inf] 
			List<PublisherType> publishers = core.getPublisher();
			if(publishers.size() > 0){
				List<Publisher> finalPublisher = new ArrayList<Publisher>();
				for(PublisherType p : publishers){
					finalPublisher.add(new Publisher(p));
				}
				jpsCore.setPublishers(finalPublisher);
			}

			//CreationDate
			XMLGregorianCalendar creationDate = core.getCreationDate();
			if(creationDate != null){
				jpsCore.setCreationDate(creationDate);
			}

			//ModifiedDate
			XMLGregorianCalendar modifiedDate = core.getModifiedDate();
			if(modifiedDate != null){
				jpsCore.setModifiedDate(modifiedDate);
			}

			//Description
			String descr = core.getDescription();
			if(descr != null && !StringUtils.isBlank(descr))
				jpsCore.setDescription(descr.replaceAll("[ ][ ]|[ ]\n", ""));

			//RightsDescription
			RightsDescriptionType rightsDescr = core.getRightsDescription();
			if(rightsDescr != null)
				jpsCore.setRightsDescription(new RightsDescription(rightsDescr));

			//Source [0-inf]
			List<SourceType> sourcesRead = core.getSource(); //may contain multiple sources
			if(sourcesRead.size() > 0){
				List<Source> sources = new ArrayList<Source>(); //Final structure of sources
				for(SourceType st : sourcesRead){ //iterate through sources
					sources.add(new Source(st));
				}
				jpsCore.setSource(sources);
			}

			//Keywords
			List<String> keywords = core.getKeyword();
			if(keywords.size() > 0){
				jpsCore.setKeywords(keywords);
			}

			//Title
			String title = core.getTitle();
			if(title != null && !StringUtils.isBlank(title))
				jpsCore.setTitle(title);

			//CollectionLabel
			List<String> collLabel = core.getCollectionLabel();
			if(collLabel.size() > 0)
				jpsCore.setCollectionLabel(collLabel);

			//PreferredValue
			BigInteger prefValue = core.getPreferenceValue();
			if(prefValue != null)
				jpsCore.setPreferredValue(prefValue.intValue());

			//Rating
			ControlledRatingTermType crtt = core.getRating();
			if(crtt != null)
				jpsCore.setRating(new Rating(crtt));

			//OriginalImageIdentifier
			ImageIdentifierType iit = core.getOriginalImageIdentifier();	
			if(iit != null)
				jpsCore.setOriginalImageIdentifier(new OriginalImageIdentifier(iit));

			//GPS Positioning
			GPSPositioningType gpt = core.getGPSPositioning();		
			if(gpt != null)
				jpsCore.setGpsPositioning(new GPSPositioning(gpt));

			//RegionOfInterest [0..inf]
			List<RegionOfInterestType> regions = core.getRegionOfInterest();
			if(regions.size() > 0){
				List<RegionOfInterest> regionOfInterest = new ArrayList<RegionOfInterest>();

				for(RegionOfInterestType r : regions){ //multiple regions
					regionOfInterest.add(new RegionOfInterest(r));
				}
				jpsCore.setRegionOfInterest(regionOfInterest);
			}

			//Width + Height
			jpsCore.setWidth(core.getWidth());
			jpsCore.setHeight(core.getHeight());
		}

		catch(Exception e){
			e.printStackTrace();
		}

		return jpsCore;
	}


	//General method of executing marshaling
	//Output type [0: File, 1: String]
	public static String marshal(JPSearchCore jpsCore){
		return marshal(jpsCore, null);
	}

	public static String marshal(JPSearchCore jpsCore, File output){
		//WRITE
		//to create an ImageIdentifier structure you need the following (ObjectFactory + JPSearchCoreType)
		ObjectFactory of = new ObjectFactory();
		JPSearchCoreType core = new JPSearchCoreType();

		//Identifier
		String id = jpsCore.getIdentifier();
		if(id != null)
			core.setIdentifier(id);

		//Then using the object factory which was automatically created from xjc 
		//you can create correctly the other types of objects such as PersonNameType

		//Modifiers
		List<Person> mods = jpsCore.getModifiers();	
		for(Person p : mods){
			PersonNameType person = of.createPersonNameType();	
			person.getGivenNameOrFamilyName().add(of.createPersonNameTypeGivenName(p.getGivenName()));
			person.getGivenNameOrFamilyName().add(of.createPersonNameTypeFamilyName(p.getFamilyName()));
			core.getModifiers().add(person);
		}

		//Creators
		List<Person> creators = jpsCore.getCreators();			
		for(Person p : creators){
			PersonNameType person = of.createPersonNameType();	
			person.getGivenNameOrFamilyName().add(of.createPersonNameTypeGivenName(p.getGivenName()));
			person.getGivenNameOrFamilyName().add(of.createPersonNameTypeFamilyName(p.getFamilyName()));
			core.getCreators().add(person);
		}

		//Publishers
		List<Publisher> publishers = jpsCore.getPublishers();
		for(Publisher p : publishers){
			PublisherType pt = of.createPublisherType();

			//PersonName
			PersonNameType pubName = of.createPersonNameType();	
			pubName.getGivenNameOrFamilyName().add(of.createPersonNameTypeGivenName(p.getPersonGivenName()));
			pubName.getGivenNameOrFamilyName().add(of.createPersonNameTypeFamilyName(p.getPersonFamilyName()));
			pt.getPersonNameOrOrganizationInformation().add(pubName);

			//Organization Information			
			CDOrganizationType cdo = p.getOrgInfo();
			if(cdo != null){
				OrganizationType orgType = of.createOrganizationType();

				for(String orgName : cdo.getNames()){ //Org. Name
					orgType.getName().add(orgName);
				}

				PlaceType address = of.createPlaceType();// Org. Address

				for(String addName : cdo.getAddress().getName()){
					address.getName().add(addName);
				}
				for(String addDescr : cdo.getAddress().getDescription()){
					address.getDescription().add(addDescr);
				}	
				orgType.setAddress(address);			
				pt.getPersonNameOrOrganizationInformation().add(orgType);				
			}
			core.getPublisher().add(pt);
		}		

		//Creation Date
		core.setCreationDate(jpsCore.getCreationDate());

		//Modified Date
		core.setModifiedDate(jpsCore.getModifiedDate());

		//Description
		String descr = jpsCore.getDescription();
		core.setDescription(descr);

		//Rights Description
		RightsDescriptionType rightsDescType = of.createRightsDescriptionType();
		RightsDescription rd = jpsCore.getRightsDescription();
		if(rd != null){
			rightsDescType.setRightsDescriptionInformation(rd.getRightsDescriptionInformation());
			rightsDescType.setDescription(rd.getDescription());
			rightsDescType.setActualRightsDescriptionReference(rd.getActualRightsDescriptionReference());
			rightsDescType.setActualRightsDescription(rd.getActualRightsDescription());
			core.setRightsDescription(rightsDescType);
		}

		//Source
		for(Source s : jpsCore.getSource()){
			SourceType st = of.createSourceType();

			//SourceElementType
			st.setSourceElementType(s.getSourceElementType());

			//SourceElement
			for(SourceElement se : s.getSourceElements()){
				SourceType.SourceElement newSE = of.createSourceTypeSourceElement();
				newSE.setSourceElementTitle(se.getSourceElementTitle());
				newSE.setSourceElementDescription(se.getSourceElementDescription());
				newSE.setSourceElementIdentifier(se.getSourceElementIdentifier());				
				st.getSourceElement().add(newSE);
			}

			//CreationMethod
			st.setCreationMethod(s.getCreationMethod());

			//Creation Description
			st.setCreationDescription(s.getCreationDescription());

			core.getSource().add(st);
		}		

		//Keywords
		for(String k : jpsCore.getKeywords()){
			core.getKeyword().add(k);
		}

		//Title
		String title = jpsCore.getTitle();
		core.setTitle(title);

		//Collection Label
		for(String c : jpsCore.getCollectionLabel()){
			core.getCollectionLabel().add(c);
		}

		//Preferred Value
		int pv = jpsCore.getPreferredValue();
		if(pv > 0)
			core.setPreferenceValue(new BigInteger(Integer.toString(pv)));

		//Rating
		ControlledRatingTermType crtt = of.createControlledRatingTermType();
		Rating rating = jpsCore.getRating();
		if(rating != null){
			crtt.setLabelDefinition(rating.getLabelDefinition());
			crtt.setLabelValue(rating.getLabelValue());
			core.setRating(crtt);
		}

		//OriginalImageIdentifier
		ImageIdentifierType iit = of.createImageIdentifierType();
		OriginalImageIdentifier oii = jpsCore.getOriginalImageIdentifier();
		if(oii != null){
			iit.setOriginationOfID(oii.getOriginationofID());
			iit.setIdentifier(oii.getIdentifier());
			core.setOriginalImageIdentifier(iit);
		}

		//GPSPositioning
		GPSPositioningType gpsPosType = of.createGPSPositioningType();
		GPSPositioning gpsp = jpsCore.getGpsPositioning();
		if(gpsp != null){
			double longitude = gpsp.getLongitude();
			double latitude = gpsp.getLatitude();
			double altitude = gpsp.getAltitude();
			gpsPosType.setLongitude(longitude);			
			gpsPosType.setLatitude(latitude);
			gpsPosType.setAltitude(altitude);
			core.setGPSPositioning(gpsPosType);
		}

		//RegionOfInterest
		for(RegionOfInterest roi : jpsCore.getRegionOfInterest()){
			RegionOfInterestType newRegOfInt = of.createRegionOfInterestType();

			//RegionOfInterest/RegionLocator
			RegionLocator rl = roi.getRegionLocator();
			RegionLocatorType newRegionLocType = of.createRegionLocatorType();

			//RegionLocator/Region
			for(RegionLocator.Region r : rl.getRegions()){
				IntegerMatrixType newIntegerMT = new IntegerMatrixType();
				//RegionLocator/Region/Area
				for(Integer i : r.getArea()){ 
					newIntegerMT.getValue().add(i);					
				}
				//RegionLocator/Region/Dimension
				newIntegerMT.setDim(new BigInteger(Integer.toString(r.getDimension())));
				newRegionLocType.getRegion().add(newIntegerMT);
			}			

			newRegOfInt.setRegionLocator(newRegionLocType);

			//RegionOfInterest/Description
			newRegOfInt.setDescription(roi.getDescription());

			//RegionOfInterest/Keywords
			for(String k : roi.getKeywords()){
				newRegOfInt.getKeyword().add(k);
			}

			//RegionOfInterest/Title
			newRegOfInt.setTitle(roi.getTitle());

			//RegionOfInterest/ContentDescription
			RegionOfInterestType.ContentDescription newCD = of.createRegionOfInterestTypeContentDescription();
			ContentDesciption cd = roi.getContentDescription();

			if(cd != null){
				//RegionOfInterest/ContentDescription/Person (Name, Affiliation, Address, Description, Nationality)
				for(ContentDesciption.CDPerson p : cd.getPerson()){
					PersonType newPT = of.createPersonType();

					//RegionOfInterest/ContentDescription/Person/Name
					PersonNameType newPNT = of.createPersonNameType();
					if(p.getNames()!=null){
						newPNT.getGivenNameOrFamilyName().add(of.createPersonNameTypeGivenName(p.getNames().getGivenName()));
						newPNT.getGivenNameOrFamilyName().add(of.createPersonNameTypeFamilyName(p.getNames().getFamilyName()));
					}
					//newPNT.setLang();
					newPT.setName(newPNT);

					//RegionOfInterest/ContentDescription/Person/Affiliation (Name + Address)
					for(ContentDesciption.CDOrganizationType o : p.getAffiliation()){
						OrganizationType newOT = of.createOrganizationType();

						//RegionOfInterest/ContentDescription/Person/Affiliation/Name
						for(String n : o.getNames()){
							newOT.getName().add(n);
						}

						//RegionOfInterest/ContentDescription/Person/Affiliation/Address
						PlaceType newPlaceType = of.createPlaceType();
						if(o.getAddress()!=null){
							for(String n : o.getAddress().getName()){
								newPlaceType.getName().add(n);
							}
							for(String d : o.getAddress().getDescription()){
								newPlaceType.getDescription().add(d);
							}
						}
						newOT.setAddress(newPlaceType);	

						newPT.getAffiliation().add(newOT);
					}

					//RegionOfInterest/ContentDescription/Person/Address
					PlaceType newAddress = of.createPlaceType();
					if(p.getAddress()!=null){
						for(String n : p.getAddress().getName()){
							newAddress.getName().add(n);
						}
						for(String d : p.getAddress().getDescription()){
							newAddress.getDescription().add(d);
						}
					}

					newPT.setAddress(newAddress);

					//RegionOfInterest/ContentDescription/Person/Description
					newPT.setDescription(p.getDescription());

					//RegionOfInterest/ContentDescription/Person/Nationality
					newPT.setNationality(p.getNationality());

					newCD.getPerson().add(newPT);
				}

				//RegionOfInterest/ContentDescription/Object (Name, Description, Label)
				for(ContentDesciption.CDObject o : cd.getObject()){
					ObjectType newObjT = of.createObjectType();

					//RegionOfInterest/ContentDescription/Object/Name
					for(String n : o.getName()){
						newObjT.getName().add(n);	
					}
					//RegionOfInterest/ContentDescription/Object/Description
					newObjT.setDescription(o.getDescription());
					//RegionOfInterest/ContentDescription/Object/Label
					newObjT.setLabel(o.getLabel());

					newCD.getObject().add(newObjT);
				}

				//RegionOfInterest/ContentDescription/Place (Name, Description)
				for(Place p : cd.getPlace()){
					PlaceType newPlace = of.createPlaceType();
					for(String n : p.getName()){
						newPlace.getName().add(n);
					}
					for(String d : p.getDescription()){
						newPlace.getDescription().add(d);
					}
					newCD.getPlace().add(newPlace);
				}

				//RegionOfInterest/ContentDescription/Event (Label, Description)
				for(ContentDesciption.CDEvent e : cd.getEvent()){
					EventType newET = of.createEventType();
					for(String l : e.getLabel()){
						newET.getLabel().add(l);
					}
					for(String d : e.getDescription()){
						newET.getDescription().add(d);
					}
					newCD.getEvent().add(newET);
				}

				newRegOfInt.setContentDescription(newCD);
			}
			//RegionOfInterest/ExternalDescription (TagName, LiteralValue, ResourceValue, StructuredValue)
			for(ExternalDescription ed : roi.getExternalDescription()){
				ExternalDescriptonType newEDT = of.createExternalDescriptonType();

				//RegionOfInterest/ExternalDescription/TagName
				TagName newTagName = of.createExternalDescriptonTypeTagName();
				newTagName.setFromNamespace(ed.getTagName().getFromNameSpace());
				newTagName.setFromNamespacePrefix(ed.getTagName().getFromNameSpacePrefix());
				newTagName.setContent(ed.getTagName().getContent());
				newEDT.setTagName(newTagName);

				//RegionOfInterest/ExternalDescription/LiteralValue
				LiteralValue newLitValue = of.createExternalDescriptonTypeLiteralValue();
				newLitValue.setFromControlledVocabularyOfLiterals(ed.getLiteralValue().getFromControlledVocabularyOfLiterals());
				newLitValue.setFromGrammarOfLiterals(ed.getLiteralValue().getFromGrammarOfLiterals());
				newLitValue.setContent(ed.getLiteralValue().getContent());
				newEDT.setLiteralValue(newLitValue);

				//RegionOfInterest/ExternalDescription/ResourceValue (FromNamespace, Content)
				ResourceValue newRV = of.createExternalDescriptonTypeResourceValue();
				newRV.setFromNamespace(ed.getResourceValue().getFromNameSpace());
				newRV.setContent(ed.getResourceValue().getContent());
				newEDT.setResourceValue(newRV);

				//RegionOfInterest/ExternalDescription/StructuredValue
				StructuredValue newSV = of.createExternalDescriptonTypeStructuredValue();
				newSV.setFromNamespace(ed.getStructuredValue().getFromNameSpace());
				for(Object c : ed.getStructuredValue().getContent()){
					if(c instanceof String){
						newSV.getContent().add(StringEscapeUtils.unescapeXml(c.toString()));
					}
					else
						newSV.getContent().add(c);
				}
				newEDT.setStructuredValue(newSV);

				newRegOfInt.getExternalDescription().add(newEDT);
			}

			core.getRegionOfInterest().add(newRegOfInt);
		}	

		//Width + Height
		core.setWidth(jpsCore.getWidth());
		core.setHeight(jpsCore.getHeight());

		//Final Element required before marshalling
		JAXBElement<JPSearchCoreType> imageDescription = of.createImageDescription(core);

		//Marshalling process
		try{
			JAXBContext jaxbContext = JAXBContext.newInstance ("jpsearchcore");
			Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
			jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);	

			if(output != null)
				jaxbMarshaller.marshal(imageDescription, output);	

			StringWriter sw = new StringWriter();
			jaxbMarshaller.marshal(imageDescription, sw);
			return sw.toString();
		}
		catch(JAXBException e){
			e.printStackTrace();
			return null;
		}
		catch(Exception e1){
			return null;
		}
	}
}
